package com.digitalchina.dcn.dcss.portal.protocol;

import io.netty.buffer.ByteBuf;
import org.apache.spark.network.protocol.Encodable;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Created by Administrator on 2016/8/22.
 */
public class PortalMessage implements Encodable{

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("portal message -------------------------\n");
        builder.append("    version:    ").append(VERSION).append("\n");
        builder.append("    type:   ").append(type).append("\n");
        builder.append("    authType:   ").append(authType).append("\n");
        builder.append("    serialNo:   ").append(serialNumber).append("\n");
        builder.append("    reqID:    ").append(requestID).append("\n");
        builder.append("    userIP:    ").append(userIP).append("\n");
        builder.append("    errorCode:    ").append(errorCode).append("\n");
        builder.append("    attributeNUm:   ").append(attributes().size()).append("\n");

        for (PortalAttribute attr: attributes()) {
            builder.append(attr).append("\n");
        }
        builder.append("-----------------------------------------\n");

        return builder.toString();
    }

    public InetSocketAddress getAcAddress() {
        return acAddress;
    }

    public void setAcAddress(InetSocketAddress acAddress) {
        this.acAddress = acAddress;
    }

    /** 接收Portal请求消息的AC地址 */
    private InetSocketAddress acAddress;

    /** portal协议的版本号，默认为0x01 */
    public static final byte VERSION = 0x01;

    /** 认证类型，目前支持PAP、CHAP  */
    private AuthType authType = AuthType.PAP;

    /** Used to identify this portal message type. */
    private Type type;

    /** sn占两个字节  */
    private int serialNumber;

    private int requestID;

    private UserIP userIP;

    private ErrorCode errorCode;

    private List<PortalAttribute> attributes;

    public PortalMessage(Type type, AuthType authType, int sn, int reqID, UserIP ip, ErrorCode errorCode) {
        this.type = type;
        this.authType = authType;
        this.serialNumber = sn;
        this.requestID = reqID;
        this.userIP = ip;
        this.errorCode = errorCode;
    }

    public AuthType authType() { return authType; }

    public int serialNumber() {
        return serialNumber;
    }

    public int requestID() {
        return requestID;
    }

    public Type type() {
        return type;
    }

    public List<PortalAttribute> attributes() {
        if (attributes != null && attributes.size() != 0) {
            return attributes;
        }
        return Collections.emptyList();
    }

    public UserIP userIP() {
        return userIP;
    }

    public ErrorCode errorCode() {
        return errorCode;
    }

    @Override
    public int encodedLength() {
        if (attributes != null && attributes.size() != 0) {
            int attributeLength = 0;
            for (PortalAttribute attribute : attributes) {
                attributeLength += attribute.encodedLength();
            }

            return 16 + attributeLength;
        } else {
            // Portal报文头，16个字节
            return 16;
        }
    }

    @Override
    public void encode(ByteBuf buf) {
        // 写入portal 协议版本号
        buf.writeByte(VERSION);
        // 写入portal 报文类型
        type.encode(buf);
        // 写入认证类型
        authType.encode(buf);
        buf.writeByte(0);
        // 写入SN号
        buf.writeChar(serialNumber);
        // 写入request ID
        buf.writeChar(requestID);
        // 写入无线用户 IP
        userIP.encode(buf);
        // 写入无线用户 Port，目前为0
        buf.writeChar(0);
        // 写入error code
        errorCode.encode(buf);
        // 写入 attribute number
        buf.writeByte(attributes().size());

        // 写入Attribute
        for(PortalAttribute attribute : attributes()) {
            attribute.encode(buf);
        }
    }

     public static PortalMessage decode(ByteBuf buf) {
        // 读取 portal version
        buf.readByte();
        // 读取 消息类型
        Type type = Type.decode(buf);
        // 读取 认证类型
        AuthType authType = AuthType.decode(buf);
        // 读取保留字段 0
        buf.readByte();
        // 读取 SN号
        char sn = buf.readChar();
        // 读取 request ID
        char reqID = buf.readChar();
        // 读取 认证用户的IP
        UserIP ip = UserIP.decode(buf);
        // 读取 认证用户的端口，目前为0
        buf.readChar();
        // 读取 errorCode
        ErrorCode errorCode = ErrorCode.decode(type, buf);
        // 读取 属性个数
        int attrNum = buf.readByte();

        PortalMessage message = new PortalMessage(type, authType, sn, reqID, ip, errorCode);

        while(attrNum > 0) {
            PortalAttribute attribute = PortalAttribute.decode(buf);
            message.addAttribute(attribute);
            --attrNum;
        }

        return message;
    }

    public void addAttribute(PortalAttribute attribute) {
        if (attributes == null) {
            attributes = new ArrayList<PortalAttribute>();
        }
        attributes.add(attribute);
    }

    /** Preceding every serialized Message is its type, which allows us to deserialize it. */
    public enum Type implements Encodable {
        ChallengeReq(1), ChallengeAck(2), AuthReq(3),
        AuthAck(4), LogoutReq(5), LogoutAck(6),
        AuthAckAff(7), LogoutNTF(8), InfoReq(9),
        InfoAck(10);

        private final byte id;

        Type(int id) {
            assert id < 128 : "Cannot have more than 128 message types";
            this.id = (byte) id;
        }

        public byte id() { return id; }

        @Override public int encodedLength() { return 1; }

        @Override public void encode(ByteBuf buf) { buf.writeByte(id); }

        public static Type decode(ByteBuf buf) {
            byte id = buf.readByte();
            switch (id) {
                case 1: return ChallengeReq;
                case 2: return ChallengeAck;
                case 3: return AuthReq;
                case 4: return AuthAck;
                case 5: return LogoutReq;
                case 6: return LogoutAck;
                case 7: return AuthAckAff;
                case 8: return LogoutNTF;
                case 9: return InfoReq;
                case 10: return InfoAck;
                default: throw new IllegalArgumentException("Unknown message type: " + id);
            }
        }
    }

    public enum AuthType implements Encodable {
        CHAP(0), PAP(1);

        private final byte id;

        AuthType(int id) {
            assert id < 2 : "Cannot have more than 2 message types";
            this.id = (byte) id;
        }

        public byte id() { return id; }

        @Override public int encodedLength() { return 1; }

        @Override public void encode(ByteBuf buf) { buf.writeByte(id); }

        public static AuthType decode(ByteBuf buf) {
            byte id = buf.readByte();
            switch (id) {
                case 0: return CHAP;
                case 1: return PAP;
                default: throw new IllegalArgumentException("Unknown auth type: " + id);
            }
        }
    }

    public static class UserIP implements Encodable {

        private byte[] ip = new byte[4];

        public UserIP(String ipStr) {
            int i = 0;
            for (String num : ipStr.split("\\.")) {
//                ip[i] = Integer.valueOf(num);
                ip[i] = (byte) Short.parseShort(num);;
                i++;
            }
        }

        public UserIP(byte[] ipBytes) {
            assert ipBytes.length == 4 : "user ip must be just 4 bytes!";
            ip = ipBytes;
        }

        @Override
        public int encodedLength() {
            return 4;
        }

        @Override
        public void encode(ByteBuf buf) {
            for (int num : ip) {
                buf.writeByte(num);
            }
        }

        public static UserIP decode(ByteBuf buf) {
            byte[] ipBytes = new byte[4];
            buf.readBytes(ipBytes);
            return new UserIP(ipBytes);
        }

        public byte[] getIPBytes() { return ip; }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;

            if (obj == null)
                return false;

            if (!(obj instanceof UserIP))
                return false;

            byte[] others = ( (UserIP)obj ).getIPBytes();
            for (int i = 0; i < 4; i++) {
                if (ip[i] != others[i])
                    return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            return toString().hashCode();
        }

        @Override
        public String toString() {
            int[] tmp = new int[4];
            int i = 0;
            for (byte num : ip) {
                tmp[i] = (num + 256) % 256;
                i++;
            }

            return tmp[0] + "." + tmp[1] + "." + tmp[2] + "." + tmp[3];
        }
    }

    public enum ErrorCode implements Encodable {
        // Type = 2, challenge应答报文
        ChallengeSuccess(0), ChallengeReject(1), ChallengeExist(2),
        ChallengeBusy(3), ChallengeFailure(4),
        // Type = 4, 认证应答报文
        AuthSuccess(0), AuthReject(1), AuthExist(2),
        AuthBusy(3), AuthFailure(4),
        // Type = 5, portal server -> AC 报文
        LogoutRequest(0), Timeout(1),
        // Type = 6, 请求下线应答报文
        LogoutSuccess(0), LogoutReject(1), LogoutFailure(2),
        Default(0);

//        private int type;
        private int errorCode;

        ErrorCode(int errorCode) {
//            this.type = type;
            this.errorCode = errorCode;
        }

        @Override
        public int encodedLength() { return 1; }

        @Override
        public void encode(ByteBuf buf) { buf.writeByte(errorCode); }

        public static ErrorCode decode(Type type, ByteBuf buf) {
            byte id = buf.readByte();
            if (type == Type.ChallengeAck) {
                switch (id) {
                    case 0: return ChallengeSuccess;
                    case 1: return ChallengeReject;
                    case 2: return ChallengeExist;
                    case 3: return ChallengeBusy;
                    case 4: return ChallengeFailure;
                    default: throw new IllegalArgumentException("Unknown auth type: " + id);
                }
            } else if (type == Type.AuthAck) {
                switch (id) {
                    case 0: return AuthSuccess;
                    case 1: return AuthReject;
                    case 2: return AuthExist;
                    case 3: return AuthBusy;
                    case 4: return AuthFailure;
                    default: throw new IllegalArgumentException("Unknown auth type: " + id);
                }
            } else if (type == Type.LogoutReq) {
                switch(id) {
                    case 0: return LogoutRequest;
                    case 1: return Timeout;
                    default: throw new IllegalArgumentException("Unknown auth type: " + id);
                }
            } else if (type == Type.LogoutAck) {
                switch(id) {
                    case 0: return LogoutSuccess;
                    case 1: return LogoutReject;
                    case 2: return LogoutFailure;
                    default: throw new IllegalArgumentException("Unknown auth type: " + id);
                }
            }

            return Default;
        }
    }





























}
